#include "InterfazPeer.h"
#include "Pieza.h"

/*Hay que hacer un include, debido al dynamic cast que se usa en el constructor*/
#include "ConexionPeer.h"

InterfazPeer::InterfazPeer(ConexionPeer *conexion, const string &peerId, unsigned long int cantidadPiezas, Condition *conditionError, Mutex *mutexPiezas, list<InformacionPieza*> *piezas, Mutex *mutexPiezasABajarOBajadas, list<unsigned long int> *piezasPorBajar, list<unsigned long int> *piezasBajadas, const string &server, const unsigned long int puerto)
: tRecibidor(this, dynamic_cast<Conexion*>(conexion), &this->protocolo, &this->conditionColaEntrada, &this->colaEntrada),
tEnviador(this, dynamic_cast<Conexion*>(conexion), &this->conditionColaSalida, &this->colaSalida),
tManejador(this, &this->conditionColaEntrada, &this->colaEntrada, mutexPiezas, piezas, mutexPiezasABajarOBajadas, piezasPorBajar, piezasBajadas, &this->conditionPeerChoking, &this->conditionTerminoBajado)
{

	this->conexion = conexion;

	this->server = server;
	this->puerto = puerto;

	this->peerId = peerId;
	this->cantidadPiezas = cantidadPiezas;

	this->conditionError = conditionError;

	this->conditionColaEntrada = conditionColaEntrada;
	this->colaEntrada = colaEntrada;

	if(conexion->getConectado()){
		tEnviador.start();
		tRecibidor.start();
		tManejador.start();
		this->threadsArrancados = true;
		this->activo = true;
	} else {
		cerr << "no conectado, error " << endl;
		threadsArrancados = false;
		this->enviarSenialNoActivo();
	}
}

InterfazPeer::~InterfazPeer()
{
	if(threadsArrancados){
		this->tRecibidor.join();
		this->tEnviador.join();
		this->tManejador.join();
	}
}

void InterfazPeer::acolarParaSalida(const string &s)
{
	this->conditionColaSalida.mutexLock();

	this->colaSalida.push(s);
	if(this->colaSalida.size() == 1){
		this->conditionColaSalida.signal();
	}

	this->conditionColaSalida.mutexUnlock();
}

void InterfazPeer::desconectar()
{
	this->tEnviador.stop();
	this->tRecibidor.stop();
	this->tManejador.stop();

	this->shutdownAndClose();
}

void InterfazPeer::mantenerVivo()
{
	//TODO que se guarde el tiempo desde cuando se mando el ultimo mensaje, y solo en caso de que sea mayor a dos mins mandarlo, si no no mandar y devolver falso
	this->acolarParaSalida(this->protocolo.mantenerVivo());
}

void InterfazPeer::choke()
{
	this->estoyChoking = true;
	this->acolarParaSalida(this->protocolo.choke());
}

void InterfazPeer::unchoke()
{
	this->estoyChoking = false;
	this->acolarParaSalida(this->protocolo.unchoke());
}

void InterfazPeer::interesado()
{
	this->estoyInteresado = true;
	this->acolarParaSalida(this->protocolo.interesado());
}

void InterfazPeer::noInteresado()
{
	this->estoyInteresado = false;
	this->acolarParaSalida(this->protocolo.noInteresado());
}

bool InterfazPeer::tengo(const unsigned long int indicePieza)
{
	this->acolarParaSalida(this->protocolo.tengo(indicePieza));
	return true;
}

bool InterfazPeer::bitfield(const list<unsigned long int> &piezas)
{
	this->acolarParaSalida(this->protocolo.bitfield(piezas, this->cantidadPiezas));
	cerr << " se envio el bitfield " << endl;
	return true;
}

bool InterfazPeer::pedido(const unsigned long int indicePieza, const unsigned long int offsetPieza, const unsigned long int longitud)
{
	if(!this->getPeerChoking()){
		this->acolarParaSalida(this->protocolo.pedido(indicePieza, offsetPieza, longitud));
		return true;
	} else {
		return false;
	}
}

bool InterfazPeer::pieza(const unsigned long int indicePieza, const unsigned long int offsetPieza, const string &informacion)
{
	if(!this->getPeerChoking()){
		this->acolarParaSalida(this->protocolo.pieza(indicePieza, offsetPieza, informacion));
		return true;
	} else {
		return false;
	}

}

bool InterfazPeer::cancelar(const unsigned long int indicePieza, const unsigned long int offsetPieza, const unsigned long int longitud)
{
	if(!this->peerChoking){
		this->acolarParaSalida(this->protocolo.cancelar(indicePieza, offsetPieza, longitud));
		return true;
	} else {
		return false;
	}
}

void InterfazPeer::shutdownAndClose()
{
	this->conexion->shutdownAndClose(); //TODO ver si sirve, visto que hay que hacer un cancelar de los threads, bien.
}

string InterfazPeer::getServer() const{
	return this->server;
}

unsigned long int InterfazPeer::getPuerto() const{
	return this->puerto;
}

string InterfazPeer::getPeerId() const{
	return this->peerId;
}

bool InterfazPeer::getEstoyChoking()
{
	this->mutexEstoyChoking.lock();
	bool aux = this->estoyChoking;
	this->mutexEstoyChoking.unlock();

	return aux;
}

void InterfazPeer::setEstoyChoking(const bool estoyChoking){
	this->mutexEstoyChoking.lock();
	this->estoyChoking = estoyChoking;
	this->mutexEstoyChoking.unlock();
}

bool InterfazPeer::getEstoyInteresado()
{
	this->mutexEstoyInteresado.lock();
	bool aux = this->estoyInteresado;
	this->mutexEstoyInteresado.unlock();

	return aux;
}

void InterfazPeer::setEstoyInteresado(const bool estoyInteresado){
	this->mutexEstoyInteresado.lock();
	this->estoyInteresado = estoyInteresado;
	this->mutexEstoyInteresado.unlock();
}

bool InterfazPeer::getPeerChoking()
{
	this->mutexPeerChoking.lock();
	bool aux = this->peerChoking;
	this->mutexPeerChoking.unlock();

	return aux;
}

void InterfazPeer::setPeerChoking(const bool peerChoking){
	this->mutexPeerChoking.lock();
	this->peerChoking = peerChoking ;
	this->mutexPeerChoking.unlock();
}

bool InterfazPeer::getPeerInteresado()
{
	this->mutexPeerInteresado.lock();
	bool aux = this->peerInteresado;
	this->mutexPeerInteresado.unlock();

	return aux;
}

void InterfazPeer::setPeerInteresado(const bool peerInteresado)
{
	this->mutexPeerInteresado.lock();
	this->peerInteresado = peerInteresado;
	this->mutexPeerInteresado.unlock();
}

bool InterfazPeer::getActivo()
{

	this->mutexActivo.lock();
	bool aux = this->activo;
	this->mutexActivo.unlock();

	return aux;
}

void InterfazPeer::setActivo(const bool activo)
{

	this->mutexActivo.lock();
	this->activo = activo;
	this->mutexActivo.unlock();
}


void InterfazPeer::enviarSenialNoActivo()
{
	if (this->getActivo()) {
		cerr << "Peer desconectado " << this->peerId << endl;
		this->desconectar();
		this->setActivo(false);
		this->conditionError->signal();
	}
}

Pieza* InterfazPeer::getPiezaActual() {

	Pieza* aux;

	this->mutexPiezaActual.lock();
	aux = this->piezaActual;
	this->mutexPiezaActual.unlock();

	return aux;
}

void InterfazPeer::setPiezaActual(Pieza* piezaActual) {

	this->mutexPiezaActual.lock();
	this->piezaActual = piezaActual;
	this->mutexPiezaActual.unlock();
}

bool InterfazPeer::estaBajando() {

	this->mutexPiezaActual.lock();
	bool aux = (this->piezaActual != NULL);
	this->mutexPiezaActual.unlock();

	return aux;
}
